Universidade de Brasília - PPCA
Aluno: Paulo Célio Soares da Silva Júnior - Matrícula: 220005605
Curso: AEDI 1/2022
Prof. João Gabriel de Moraes Souza
import pandas as pd
import pandas_datareader.data as web
import numpy as np
import matplotlib.pyplot as plt
import plotly.express as px
import seaborn as sns
from scipy import optimize
No primeiro dia de cada mês, são recomendados 10 ativos para investir, selecionados por analistas da corretora NuInvest, que compõem uma carteira denominada Top 10 recomendações. A recomendação dos analistas é que o investimento seja distribuído igualmente, ou seja, em cada um desses ativos, aplica-se 10% do total investido.
Para o mês de julho de 2022, a corretora indicou os seguintes ativos para composição da carteira:
![]() |
Petrorio PRIO3 |
![]() |
JHSF Part JHSF3 |
![]() |
Santander SANB11 |
![]() |
Telefônica Brasil VIVT3 |
![]() |
Eneva ENEV3 |
![]() |
Multiplan MULT3 |
![]() |
Simpar SIMH3 |
![]() |
Unipar UNIP6 |
![]() |
Santos Brp STBP3 |
![]() |
Gerdau Met GOAU4 |
A partir dessa carteira, o dataset de estudo foi construído a fim de obter, como amostras, os valores de mercado dessas ações desde 2015 até os dias atuais. A título de comparação, o dataset também incorpora o índice IBOVESPA (^BVSP).
Para conhecer a API, são obtidos dados de uma ação negociada na B3, no caso da Telefônica (VIVT3). A fonte dos dados a ser utilizada nesta análise é o Yahoo Finance.Em seguida, são obtidos os 5 primeiros registros para teste e exibição da estrutura do DataFrame Pandas gerado pelo serviço. O serviço retorna em sua estrutura as seguintes colunas:
telefonica_df = web.DataReader(name="VIVT3.SA",
data_source="yahoo", start="2000-01-01")
telefonica_df.head()
| High | Low | Open | Close | Volume | Adj Close | |
|---|---|---|---|---|---|---|
| Date | ||||||
| 2000-01-03 | 24.990000 | 23.700001 | 24.990000 | 24.000000 | 98.0 | 7.796522 |
| 2000-01-04 | 23.700001 | 22.750000 | 23.100000 | 23.350000 | 228.0 | 7.585369 |
| 2000-01-05 | 23.700001 | 22.000000 | 23.700001 | 23.610001 | 120.0 | 7.669831 |
| 2000-01-06 | 24.129999 | 23.100000 | 23.400000 | 24.129999 | 123.0 | 7.838752 |
| 2000-01-07 | 24.250000 | 23.600000 | 24.000000 | 24.250000 | 266.0 | 7.877736 |
A seguir, os códigos dos ativos são reunidos em uma lista para montagem do DataFrame do Pandas com preço de fechamento de cada um dos ativos que compõem a carteira. A data de início é 01/01/2015 e a de término é a data corrente.
acoes = ["PRIO3.SA", "JHSF3.SA", "SANB11.SA", "VIVT3.SA", "ENEV3.SA", "MULT3.SA", "SIMH3.SA", "UNIP6.SA", "STBP3.SA", "GOAU4.SA", "^BVSP"]
acoes
['PRIO3.SA', 'JHSF3.SA', 'SANB11.SA', 'VIVT3.SA', 'ENEV3.SA', 'MULT3.SA', 'SIMH3.SA', 'UNIP6.SA', 'STBP3.SA', 'GOAU4.SA', '^BVSP']
acoes_df = pd.DataFrame()
for acao in acoes:
acoes_df[acao[:5]] = web.DataReader(acao, data_source='yahoo', start='2015-01-01')['Close']
acoes_df.reset_index(inplace=True) # transforma o índice original (data de negociação) em um índice numérico
acoes_df.rename(columns={'Date': 'Data de Negociação'}, inplace=True) # renomeia a coluna "Date" para "Data de Negociação"
acoes_df
| Data de Negociação | PRIO3 | JHSF3 | SANB1 | VIVT3 | ENEV3 | MULT3 | SIMH3 | UNIP6 | STBP3 | GOAU4 | ^BVSP | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2015-01-02 | 0.440000 | 2.23 | 12.720000 | 37.820000 | 5.989072 | 14.753333 | NaN | 3.357142 | 0.95 | 10.82 | 48512.0 |
| 1 | 2015-01-05 | 0.407000 | 2.15 | 12.630000 | 37.070000 | 5.561281 | 14.733333 | NaN | 3.214285 | 0.95 | 10.31 | 47517.0 |
| 2 | 2015-01-06 | 0.367000 | 2.16 | 12.720000 | 36.150002 | 5.703878 | 15.466666 | NaN | 3.285713 | 0.95 | 11.32 | 48001.0 |
| 3 | 2015-01-07 | 0.366000 | 2.16 | 13.250000 | 37.389999 | 5.846475 | 16.000000 | NaN | 3.285713 | 0.95 | 12.15 | 49463.0 |
| 4 | 2015-01-08 | 0.378000 | 2.36 | 13.030000 | 38.910000 | 5.989072 | 15.830000 | NaN | 3.285713 | 0.95 | 11.74 | 49943.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1875 | 2022-07-20 | 22.260000 | 5.81 | 28.240000 | 46.889999 | 14.750000 | 23.900000 | 9.51 | 88.879997 | 6.27 | 9.95 | 98287.0 |
| 1876 | 2022-07-21 | 22.840000 | 5.68 | 28.299999 | 46.980000 | 14.530000 | 23.740000 | 9.54 | 88.470001 | 6.22 | 9.92 | 99033.0 |
| 1877 | 2022-07-22 | 22.670000 | 5.60 | 27.530001 | 46.830002 | 14.350000 | 23.980000 | 9.55 | 88.500000 | 6.25 | 9.94 | 98925.0 |
| 1878 | 2022-07-25 | 23.610001 | 5.59 | 28.020000 | 46.419998 | 14.490000 | 23.799999 | 9.50 | 88.790001 | 6.23 | 10.01 | 100270.0 |
| 1879 | 2022-07-26 | 23.740000 | 5.36 | 27.969999 | 46.389999 | 14.410000 | 23.540001 | 9.21 | 86.400002 | 6.15 | 10.01 | 99772.0 |
1880 rows × 12 columns
O gráfico de linha abaixo ilustra o histórico de preço das ações desde 2015. Para fins de comparação de desempenho apenas dos títulos da carteira, não se considerou o IBOVESPA para renderização do gráfico.
figura = px.line(title = "Histórico do preço das ações")
for i in acoes_df.columns[1:acoes_df.shape[1] - 1]:
figura.add_scatter(x=acoes_df['Data de Negociação'] ,y=acoes_df[i], name=i)
figura.show()
Sem nenhuma verificão adicional, só pela observação do gráfico, percebe-se, em praticamente todos os ativos, no início de 2020, quedas no valor de fechamento, muito provavelmente, em decorrência do início da pandemia de COVID-19.
Para análise da taxa de retorno diário das ações, utilizou-se cálculo de log-retorno em vez de retorno linear, embora, na prática, a diferença seja pouca para dias consecutivos. O log-retorno é dado pela seguinte fórmula:
$$ \mathbb{E} [ R_i] = ln \left( \frac{P_t}{P_{t-1}} \right) $$Onde:
$ln$ = logaritmo natural
$P_t$ = preço no dia final
$P_{t-1}$ = preço no dia inicial
Inicialmente, para que fosse preservada a base de dados original, criou-se uma cópia para que as manipulações pudessem ser realizadas adequadamente. Além disso, removeu-se a coluna "Data de Negociação" para facilitar cálculos de medidas de tendência central, separatrizes e de dispersão no dataset derivado, no caso, um DataFrame do Pandas, uma vez que não faz sentido calcular retorno sobre informação de datas.
dataset = acoes_df.copy()
dataset.drop(labels = ["Data de Negociação"], axis=1, inplace=True)
dataset
| PRIO3 | JHSF3 | SANB1 | VIVT3 | ENEV3 | MULT3 | SIMH3 | UNIP6 | STBP3 | GOAU4 | ^BVSP | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.440000 | 2.23 | 12.720000 | 37.820000 | 5.989072 | 14.753333 | NaN | 3.357142 | 0.95 | 10.82 | 48512.0 |
| 1 | 0.407000 | 2.15 | 12.630000 | 37.070000 | 5.561281 | 14.733333 | NaN | 3.214285 | 0.95 | 10.31 | 47517.0 |
| 2 | 0.367000 | 2.16 | 12.720000 | 36.150002 | 5.703878 | 15.466666 | NaN | 3.285713 | 0.95 | 11.32 | 48001.0 |
| 3 | 0.366000 | 2.16 | 13.250000 | 37.389999 | 5.846475 | 16.000000 | NaN | 3.285713 | 0.95 | 12.15 | 49463.0 |
| 4 | 0.378000 | 2.36 | 13.030000 | 38.910000 | 5.989072 | 15.830000 | NaN | 3.285713 | 0.95 | 11.74 | 49943.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1875 | 22.260000 | 5.81 | 28.240000 | 46.889999 | 14.750000 | 23.900000 | 9.51 | 88.879997 | 6.27 | 9.95 | 98287.0 |
| 1876 | 22.840000 | 5.68 | 28.299999 | 46.980000 | 14.530000 | 23.740000 | 9.54 | 88.470001 | 6.22 | 9.92 | 99033.0 |
| 1877 | 22.670000 | 5.60 | 27.530001 | 46.830002 | 14.350000 | 23.980000 | 9.55 | 88.500000 | 6.25 | 9.94 | 98925.0 |
| 1878 | 23.610001 | 5.59 | 28.020000 | 46.419998 | 14.490000 | 23.799999 | 9.50 | 88.790001 | 6.23 | 10.01 | 100270.0 |
| 1879 | 23.740000 | 5.36 | 27.969999 | 46.389999 | 14.410000 | 23.540001 | 9.21 | 86.400002 | 6.15 | 10.01 | 99772.0 |
1880 rows × 11 columns
A partir do dataset preparado, aplicou-se a fómula do log-retorno utilizando-se a função de logaritmo natural da biblioteca NumPy sobre o dataset preparado, dividido por um segundo dataset com os índices originais deslocados em uma posição. Essa técnica foi utilizada para que o cálculo fosse feito utilizando-se o preço do dia corrente e o preço do dia anterior, sem a necessidade de fazer um laço por todo o dataset preparado.
dataset_dia_anterior = dataset.shift(1)
taxas_retorno = np.log(dataset / dataset_dia_anterior)
taxas_retorno
| PRIO3 | JHSF3 | SANB1 | VIVT3 | ENEV3 | MULT3 | SIMH3 | UNIP6 | STBP3 | GOAU4 | ^BVSP | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1 | -0.077962 | -0.036534 | -0.007101 | -0.020030 | -0.074108 | -0.001357 | NaN | -0.043485 | 0.000000 | -0.048282 | -0.020724 |
| 2 | -0.103451 | 0.004640 | 0.007101 | -0.025131 | 0.025318 | 0.048575 | NaN | 0.021979 | 0.000000 | 0.093457 | 0.010134 |
| 3 | -0.002729 | 0.000000 | 0.040822 | 0.033726 | 0.024693 | 0.033902 | NaN | 0.000000 | 0.000000 | 0.070758 | 0.030003 |
| 4 | 0.032261 | 0.088553 | -0.016743 | 0.039848 | 0.024098 | -0.010682 | NaN | 0.000000 | 0.000000 | -0.034327 | 0.009657 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1875 | -0.004482 | 0.012121 | -0.007057 | -0.013136 | 0.018475 | -0.001254 | 0.038590 | 0.007000 | 0.022582 | 0.002012 | 0.000427 |
| 1876 | 0.025722 | -0.022629 | 0.002122 | 0.001918 | -0.015028 | -0.006717 | 0.003150 | -0.004624 | -0.008006 | -0.003020 | 0.007561 |
| 1877 | -0.007471 | -0.014185 | -0.027585 | -0.003198 | -0.012465 | 0.010059 | 0.001048 | 0.000339 | 0.004812 | 0.002014 | -0.001091 |
| 1878 | 0.040628 | -0.001787 | 0.017642 | -0.008794 | 0.009709 | -0.007535 | -0.005249 | 0.003271 | -0.003205 | 0.007018 | 0.013505 |
| 1879 | 0.005491 | -0.042015 | -0.001786 | -0.000646 | -0.005536 | -0.010984 | -0.031002 | -0.027286 | -0.012924 | 0.000000 | -0.004979 |
1880 rows × 11 columns
Em seguida, a partir do dataset preparado, é feita uma análise descritiva onde são calculadas medidas de tendência central (média), separatrizes (quartis) e de dispersão (desvio padrão), além dos valores máximo, mínimo e quantidade de avaliação das taxas de retorno obtidas. Essas informações são exibidas como um novo DataFrame do Pandas.
taxas_retorno.describe()
| PRIO3 | JHSF3 | SANB1 | VIVT3 | ENEV3 | MULT3 | SIMH3 | UNIP6 | STBP3 | GOAU4 | ^BVSP | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 1879.000000 | 1879.000000 | 1879.000000 | 1879.000000 | 1879.000000 | 1879.000000 | 457.000000 | 1879.000000 | 1879.000000 | 1879.000000 | 1863.000000 |
| mean | 0.002122 | 0.000467 | 0.000419 | 0.000109 | 0.000467 | 0.000249 | 0.000494 | 0.001729 | 0.000994 | -0.000041 | 0.000353 |
| std | 0.047506 | 0.033657 | 0.023569 | 0.018978 | 0.034396 | 0.023804 | 0.029212 | 0.028747 | 0.037384 | 0.034234 | 0.016408 |
| min | -0.454770 | -0.198070 | -0.144726 | -0.131920 | -0.441833 | -0.253473 | -0.100117 | -0.344616 | -0.268459 | -0.239184 | -0.159930 |
| 25% | -0.019508 | -0.018495 | -0.012980 | -0.009725 | -0.010798 | -0.012260 | -0.017109 | -0.011531 | -0.009934 | -0.018100 | -0.007851 |
| 50% | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | -0.000370 | 0.000734 | 0.000000 | 0.000000 | 0.000000 | 0.000589 |
| 75% | 0.020931 | 0.016807 | 0.013628 | 0.010176 | 0.011576 | 0.012250 | 0.018658 | 0.013989 | 0.010110 | 0.019277 | 0.009264 |
| max | 0.607989 | 0.254234 | 0.125840 | 0.116196 | 0.287682 | 0.159602 | 0.077118 | 0.182322 | 1.066524 | 0.187627 | 0.130223 |
Aqui são calculadas as médias das taxas de retorno das ações de duas formas (em percentual):
medias = (taxas_retorno.sum() / len(taxas_retorno)) * 100
medias
PRIO3 0.212135 JHSF3 0.046647 SANB1 0.041913 VIVT3 0.010864 ENEV3 0.046701 MULT3 0.024853 SIMH3 0.011999 UNIP6 0.172761 STBP3 0.099348 GOAU4 -0.004139 ^BVSP 0.034959 dtype: float64
taxas_retorno.mean() * 100
PRIO3 0.212248 JHSF3 0.046672 SANB1 0.041935 VIVT3 0.010870 ENEV3 0.046726 MULT3 0.024866 SIMH3 0.049363 UNIP6 0.172852 STBP3 0.099401 GOAU4 -0.004141 ^BVSP 0.035278 dtype: float64
Aqui são calculadas as variâncias amostrais das taxas de retorno das ações de duas formas:
vars_acoes = ((taxas_retorno - taxas_retorno.mean()) ** 2).sum() / (len(taxas_retorno) - 1)
vars_acoes
PRIO3 0.002256 JHSF3 0.001132 SANB1 0.000555 VIVT3 0.000360 ENEV3 0.001182 MULT3 0.000566 SIMH3 0.000207 UNIP6 0.000826 STBP3 0.001397 GOAU4 0.001171 ^BVSP 0.000267 dtype: float64
taxas_retorno.var()
PRIO3 0.002257 JHSF3 0.001133 SANB1 0.000555 VIVT3 0.000360 ENEV3 0.001183 MULT3 0.000567 SIMH3 0.000853 UNIP6 0.000826 STBP3 0.001398 GOAU4 0.001172 ^BVSP 0.000269 dtype: float64
Por fim, é calculado o desvio padrão das taxas de retorno das ações pela função std() (normalizada com N-1) de um DataFrame do Pandas (em percentual).
taxas_retorno.std() * 100
PRIO3 4.750557 JHSF3 3.365743 SANB1 2.356878 VIVT3 1.897803 ENEV3 3.439600 MULT3 2.380383 SIMH3 2.921168 UNIP6 2.874708 STBP3 3.738410 GOAU4 3.423359 ^BVSP 1.640759 dtype: float64
A partir do dataset original, busca-se primeiramente as datas das negociações e depois realiza-se a incorporação no dataset contendo as taxas de retorno calculadas. Para isso utiliza-se a função concat() do DataFrame do Pandas.
dataset_date = acoes_df.copy()
date = dataset_date.filter(["Data de Negociação"])
date
| Data de Negociação | |
|---|---|
| 0 | 2015-01-02 |
| 1 | 2015-01-05 |
| 2 | 2015-01-06 |
| 3 | 2015-01-07 |
| 4 | 2015-01-08 |
| ... | ... |
| 1875 | 2022-07-20 |
| 1876 | 2022-07-21 |
| 1877 | 2022-07-22 |
| 1878 | 2022-07-25 |
| 1879 | 2022-07-26 |
1880 rows × 1 columns
taxas_retorno_date = pd.concat([date, taxas_retorno], axis=1)
taxas_retorno_date
| Data de Negociação | PRIO3 | JHSF3 | SANB1 | VIVT3 | ENEV3 | MULT3 | SIMH3 | UNIP6 | STBP3 | GOAU4 | ^BVSP | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2015-01-02 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1 | 2015-01-05 | -0.077962 | -0.036534 | -0.007101 | -0.020030 | -0.074108 | -0.001357 | NaN | -0.043485 | 0.000000 | -0.048282 | -0.020724 |
| 2 | 2015-01-06 | -0.103451 | 0.004640 | 0.007101 | -0.025131 | 0.025318 | 0.048575 | NaN | 0.021979 | 0.000000 | 0.093457 | 0.010134 |
| 3 | 2015-01-07 | -0.002729 | 0.000000 | 0.040822 | 0.033726 | 0.024693 | 0.033902 | NaN | 0.000000 | 0.000000 | 0.070758 | 0.030003 |
| 4 | 2015-01-08 | 0.032261 | 0.088553 | -0.016743 | 0.039848 | 0.024098 | -0.010682 | NaN | 0.000000 | 0.000000 | -0.034327 | 0.009657 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1875 | 2022-07-20 | -0.004482 | 0.012121 | -0.007057 | -0.013136 | 0.018475 | -0.001254 | 0.038590 | 0.007000 | 0.022582 | 0.002012 | 0.000427 |
| 1876 | 2022-07-21 | 0.025722 | -0.022629 | 0.002122 | 0.001918 | -0.015028 | -0.006717 | 0.003150 | -0.004624 | -0.008006 | -0.003020 | 0.007561 |
| 1877 | 2022-07-22 | -0.007471 | -0.014185 | -0.027585 | -0.003198 | -0.012465 | 0.010059 | 0.001048 | 0.000339 | 0.004812 | 0.002014 | -0.001091 |
| 1878 | 2022-07-25 | 0.040628 | -0.001787 | 0.017642 | -0.008794 | 0.009709 | -0.007535 | -0.005249 | 0.003271 | -0.003205 | 0.007018 | 0.013505 |
| 1879 | 2022-07-26 | 0.005491 | -0.042015 | -0.001786 | -0.000646 | -0.005536 | -0.010984 | -0.031002 | -0.027286 | -0.012924 | 0.000000 | -0.004979 |
1880 rows × 12 columns
figura = px.line(title = 'Histórico de retorno das ações')
for i in taxas_retorno_date.columns[1:]:
figura.add_scatter(x=taxas_retorno_date["Data de Negociação"], y=taxas_retorno_date[i], name=i)
figura.show()
Aqui é calculada a covariância entre as taxas de retorno das ações pela função cov() de um DataFrame do Pandas.
Obs.: Considerando-se as linhas e colunas da tabela abaixo, a covariância da linha com o correspondente dela mesmo nas colunas é a variância dela.
taxas_retorno.cov()
| PRIO3 | JHSF3 | SANB1 | VIVT3 | ENEV3 | MULT3 | SIMH3 | UNIP6 | STBP3 | GOAU4 | ^BVSP | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| PRIO3 | 0.002257 | 0.000403 | 0.000335 | 0.000180 | 0.000257 | 0.000335 | 0.000249 | 0.000308 | 0.000305 | 0.000550 | 0.000356 |
| JHSF3 | 0.000403 | 0.001133 | 0.000304 | 0.000154 | 0.000247 | 0.000380 | 0.000451 | 0.000226 | 0.000349 | 0.000402 | 0.000294 |
| SANB1 | 0.000335 | 0.000304 | 0.000555 | 0.000156 | 0.000147 | 0.000294 | 0.000157 | 0.000173 | 0.000195 | 0.000365 | 0.000291 |
| VIVT3 | 0.000180 | 0.000154 | 0.000156 | 0.000360 | 0.000102 | 0.000155 | 0.000070 | 0.000094 | 0.000094 | 0.000138 | 0.000138 |
| ENEV3 | 0.000257 | 0.000247 | 0.000147 | 0.000102 | 0.001183 | 0.000169 | 0.000325 | 0.000177 | 0.000238 | 0.000148 | 0.000158 |
| MULT3 | 0.000335 | 0.000380 | 0.000294 | 0.000155 | 0.000169 | 0.000567 | 0.000369 | 0.000186 | 0.000292 | 0.000291 | 0.000271 |
| SIMH3 | 0.000249 | 0.000451 | 0.000157 | 0.000070 | 0.000325 | 0.000369 | 0.000853 | 0.000201 | 0.000478 | 0.000175 | 0.000213 |
| UNIP6 | 0.000308 | 0.000226 | 0.000173 | 0.000094 | 0.000177 | 0.000186 | 0.000201 | 0.000826 | 0.000228 | 0.000273 | 0.000183 |
| STBP3 | 0.000305 | 0.000349 | 0.000195 | 0.000094 | 0.000238 | 0.000292 | 0.000478 | 0.000228 | 0.001398 | 0.000210 | 0.000202 |
| GOAU4 | 0.000550 | 0.000402 | 0.000365 | 0.000138 | 0.000148 | 0.000291 | 0.000175 | 0.000273 | 0.000210 | 0.001172 | 0.000364 |
| ^BVSP | 0.000356 | 0.000294 | 0.000291 | 0.000138 | 0.000158 | 0.000271 | 0.000213 | 0.000183 | 0.000202 | 0.000364 | 0.000269 |
Aqui é calculada a correlação entre taxas de retorno das ações pela função corr() de um DataFrame do Pandas.
taxas_retorno.corr()
| PRIO3 | JHSF3 | SANB1 | VIVT3 | ENEV3 | MULT3 | SIMH3 | UNIP6 | STBP3 | GOAU4 | ^BVSP | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| PRIO3 | 1.000000 | 0.251976 | 0.298939 | 0.199538 | 0.157096 | 0.296470 | 0.235414 | 0.225308 | 0.171714 | 0.337938 | 0.454648 |
| JHSF3 | 0.251976 | 1.000000 | 0.383435 | 0.241030 | 0.213500 | 0.474783 | 0.568460 | 0.234082 | 0.277742 | 0.348704 | 0.530412 |
| SANB1 | 0.298939 | 0.383435 | 1.000000 | 0.349420 | 0.181309 | 0.523892 | 0.256232 | 0.255393 | 0.221543 | 0.452910 | 0.750662 |
| VIVT3 | 0.199538 | 0.241030 | 0.349420 | 1.000000 | 0.155882 | 0.343063 | 0.174374 | 0.171452 | 0.132793 | 0.211781 | 0.442459 |
| ENEV3 | 0.157096 | 0.213500 | 0.181309 | 0.155882 | 1.000000 | 0.205972 | 0.487354 | 0.179281 | 0.185448 | 0.125891 | 0.278341 |
| MULT3 | 0.296470 | 0.474783 | 0.523892 | 0.343063 | 0.205972 | 1.000000 | 0.520520 | 0.271960 | 0.327813 | 0.357451 | 0.690842 |
| SIMH3 | 0.235414 | 0.568460 | 0.256232 | 0.174374 | 0.487354 | 0.520520 | 1.000000 | 0.264237 | 0.555703 | 0.252325 | 0.562866 |
| UNIP6 | 0.225308 | 0.234082 | 0.255393 | 0.171452 | 0.179281 | 0.271960 | 0.264237 | 1.000000 | 0.212404 | 0.277315 | 0.389331 |
| STBP3 | 0.171714 | 0.277742 | 0.221543 | 0.132793 | 0.185448 | 0.327813 | 0.555703 | 0.212404 | 1.000000 | 0.164470 | 0.327717 |
| GOAU4 | 0.337938 | 0.348704 | 0.452910 | 0.211781 | 0.125891 | 0.357451 | 0.252325 | 0.277315 | 0.164470 | 1.000000 | 0.646642 |
| ^BVSP | 0.454648 | 0.530412 | 0.750662 | 0.442459 | 0.278341 | 0.690842 | 0.562866 | 0.389331 | 0.327717 | 0.646642 | 1.000000 |
Por fim, o gráfico de calor ilustra a correlação entre as taxas de retorno das ações contidas na carteira. Quanto mais próximo de 1, maior é o impacto de uma ação em relação a outra. O aumento de uma ação tem o impacto proporcional no preço de outra ação, conforme o peso ilustrado no mapa de calor.
Por exemplo: o aumento de 1% em PRIO3 tem o impacto de aumento de 0,45% no ^BVSP.
plt.figure(figsize=(8,8))
sns.heatmap(taxas_retorno.corr(), annot=True);
Aqui será adicionada ao dataset criado na seção anterior a média das taxas de retorno que compõem a carteira. A idéia é preparar os dados para comparação do desempenho da carteira com a índice IBOVESPA (^BVSP).
Obs.: A média dos valores das colunas não é calculada pela função mean(), dado que as colunas podem possuir valores nulos.
carteira = taxas_retorno_date.iloc[:,1:taxas_retorno_date.shape[1] - 1] # Calcula as somas de valores de todas as colunas, com exceção da coluna "Data de Negociação" e "^BVSP"
numero_de_acoes = carteira.shape[1]
taxas_retorno_date["CARTEIRA"] = carteira.sum(axis="columns") / numero_de_acoes
taxas_retorno_date
| Data de Negociação | PRIO3 | JHSF3 | SANB1 | VIVT3 | ENEV3 | MULT3 | SIMH3 | UNIP6 | STBP3 | GOAU4 | ^BVSP | CARTEIRA | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2015-01-02 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.000000 |
| 1 | 2015-01-05 | -0.077962 | -0.036534 | -0.007101 | -0.020030 | -0.074108 | -0.001357 | NaN | -0.043485 | 0.000000 | -0.048282 | -0.020724 | -0.030886 |
| 2 | 2015-01-06 | -0.103451 | 0.004640 | 0.007101 | -0.025131 | 0.025318 | 0.048575 | NaN | 0.021979 | 0.000000 | 0.093457 | 0.010134 | 0.007249 |
| 3 | 2015-01-07 | -0.002729 | 0.000000 | 0.040822 | 0.033726 | 0.024693 | 0.033902 | NaN | 0.000000 | 0.000000 | 0.070758 | 0.030003 | 0.020117 |
| 4 | 2015-01-08 | 0.032261 | 0.088553 | -0.016743 | 0.039848 | 0.024098 | -0.010682 | NaN | 0.000000 | 0.000000 | -0.034327 | 0.009657 | 0.012301 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1875 | 2022-07-20 | -0.004482 | 0.012121 | -0.007057 | -0.013136 | 0.018475 | -0.001254 | 0.038590 | 0.007000 | 0.022582 | 0.002012 | 0.000427 | 0.007485 |
| 1876 | 2022-07-21 | 0.025722 | -0.022629 | 0.002122 | 0.001918 | -0.015028 | -0.006717 | 0.003150 | -0.004624 | -0.008006 | -0.003020 | 0.007561 | -0.002711 |
| 1877 | 2022-07-22 | -0.007471 | -0.014185 | -0.027585 | -0.003198 | -0.012465 | 0.010059 | 0.001048 | 0.000339 | 0.004812 | 0.002014 | -0.001091 | -0.004663 |
| 1878 | 2022-07-25 | 0.040628 | -0.001787 | 0.017642 | -0.008794 | 0.009709 | -0.007535 | -0.005249 | 0.003271 | -0.003205 | 0.007018 | 0.013505 | 0.005170 |
| 1879 | 2022-07-26 | 0.005491 | -0.042015 | -0.001786 | -0.000646 | -0.005536 | -0.010984 | -0.031002 | -0.027286 | -0.012924 | 0.000000 | -0.004979 | -0.012669 |
1880 rows × 13 columns
Primeiro é exibido um DataFrame do Pandas filtrado com as colunas que serão plotados no gráfico para facilitar a visualização dos números.
taxas_retorno_port = taxas_retorno_date.filter(["Data de Negociação", "CARTEIRA", "^BVSP"])
taxas_retorno_port
| Data de Negociação | CARTEIRA | ^BVSP | |
|---|---|---|---|
| 0 | 2015-01-02 | 0.000000 | NaN |
| 1 | 2015-01-05 | -0.030886 | -0.020724 |
| 2 | 2015-01-06 | 0.007249 | 0.010134 |
| 3 | 2015-01-07 | 0.020117 | 0.030003 |
| 4 | 2015-01-08 | 0.012301 | 0.009657 |
| ... | ... | ... | ... |
| 1875 | 2022-07-20 | 0.007485 | 0.000427 |
| 1876 | 2022-07-21 | -0.002711 | 0.007561 |
| 1877 | 2022-07-22 | -0.004663 | -0.001091 |
| 1878 | 2022-07-25 | 0.005170 | 0.013505 |
| 1879 | 2022-07-26 | -0.012669 | -0.004979 |
1880 rows × 3 columns
Em seguida, o gráfico é plotado contendo a comparação entre o desempenho da carteira e do IBOVESPA no mesmo período. A linha verde representa a média da taxa de retorno da carteira. Pode-se observar que os desempenhos são bastante parecidos, tendo a carteira um perfil com maior variação em função do número de títulos que a compõem ser menor do que o do IBOVESPA.
figura = px.line(title = 'Comparação de retorno Carteira x Ibovespa')
for i in taxas_retorno_port.columns[1:]:
figura.add_scatter(x=taxas_retorno_port["Data de Negociação"], y = taxas_retorno_port[i], name=i)
figura.add_hline(y=taxas_retorno_port['CARTEIRA'].mean(), line_color="green", line_dash="dot", )
figura.show()
Mais uma vez é calculada a correlação entre taxas de retorno, dessa vez entre a carteira e o IBOVESPA, como descrito na tabela abaixo.
taxas_retorno_port_corr = taxas_retorno_date.filter(["CARTEIRA", "^BVSP"])
taxas_retorno_port_corr
| CARTEIRA | ^BVSP | |
|---|---|---|
| 0 | 0.000000 | NaN |
| 1 | -0.030886 | -0.020724 |
| 2 | 0.007249 | 0.010134 |
| 3 | 0.020117 | 0.030003 |
| 4 | 0.012301 | 0.009657 |
| ... | ... | ... |
| 1875 | 0.007485 | 0.000427 |
| 1876 | -0.002711 | 0.007561 |
| 1877 | -0.004663 | -0.001091 |
| 1878 | 0.005170 | 0.013505 |
| 1879 | -0.012669 | -0.004979 |
1880 rows × 2 columns
Como na seção anterior, aqui é exibido o gráfico de calor que ilustra a correlação entre as taxas de retorno das ações contidas na carteira. Quanto mais próximo de 1, maior é o impacto de uma ação em relação a outra. O aumento de uma ação tem o impacto proporcional no preço de outra ação, conforme o peso ilustrado no mapa de calor. Se a Carteira aumenta 1%, o IBOVESPA aumenta 0,83% e vice-versa.
plt.figure(figsize=(8,8))
sns.heatmap(taxas_retorno_port_corr.corr(), annot=True);
Na seção 4.1, foi utilizada a média aritmética para obter a taxa de desempenho. Aqui serão criadas carteiras aleatórias, com pesos diferentes para verificar as melhores alocações de recursos.
Criação de um novo dataset, retirando o IBOVESPA, dado que a comparação será feita entre as carteiras aletórias.
acoes_port = acoes_df.copy()
acoes_port.drop(labels = ['^BVSP'], axis=1, inplace=True)
acoes_port
| Data de Negociação | PRIO3 | JHSF3 | SANB1 | VIVT3 | ENEV3 | MULT3 | SIMH3 | UNIP6 | STBP3 | GOAU4 | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2015-01-02 | 0.440000 | 2.23 | 12.720000 | 37.820000 | 5.989072 | 14.753333 | NaN | 3.357142 | 0.95 | 10.82 |
| 1 | 2015-01-05 | 0.407000 | 2.15 | 12.630000 | 37.070000 | 5.561281 | 14.733333 | NaN | 3.214285 | 0.95 | 10.31 |
| 2 | 2015-01-06 | 0.367000 | 2.16 | 12.720000 | 36.150002 | 5.703878 | 15.466666 | NaN | 3.285713 | 0.95 | 11.32 |
| 3 | 2015-01-07 | 0.366000 | 2.16 | 13.250000 | 37.389999 | 5.846475 | 16.000000 | NaN | 3.285713 | 0.95 | 12.15 |
| 4 | 2015-01-08 | 0.378000 | 2.36 | 13.030000 | 38.910000 | 5.989072 | 15.830000 | NaN | 3.285713 | 0.95 | 11.74 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1875 | 2022-07-20 | 22.260000 | 5.81 | 28.240000 | 46.889999 | 14.750000 | 23.900000 | 9.51 | 88.879997 | 6.27 | 9.95 |
| 1876 | 2022-07-21 | 22.840000 | 5.68 | 28.299999 | 46.980000 | 14.530000 | 23.740000 | 9.54 | 88.470001 | 6.22 | 9.92 |
| 1877 | 2022-07-22 | 22.670000 | 5.60 | 27.530001 | 46.830002 | 14.350000 | 23.980000 | 9.55 | 88.500000 | 6.25 | 9.94 |
| 1878 | 2022-07-25 | 23.610001 | 5.59 | 28.020000 | 46.419998 | 14.490000 | 23.799999 | 9.50 | 88.790001 | 6.23 | 10.01 |
| 1879 | 2022-07-26 | 23.740000 | 5.36 | 27.969999 | 46.389999 | 14.410000 | 23.540001 | 9.21 | 86.400002 | 6.15 | 10.01 |
1880 rows × 11 columns
Primeiramente é criada uma função para, a partir de um dataset e valor de investimento, calcular uma distribuição de pesos aleatória e taxas de retorno a partir da alocações de recursos feitas nas ações da carteira definida.
A função retorna:
def alocacao_ativos(dataset, dinheiro_total, seed = 0, melhores_pesos = []):
dataset = dataset.copy()
if seed != 0:
np.random.seed(seed)
if len(melhores_pesos) > 0:
pesos = melhores_pesos
else:
pesos = np.random.random(len(dataset.columns) - 1)
pesos = pesos / pesos.sum()
colunas = dataset.columns[1:]
for i in colunas:
dataset[i] = (dataset[i] / dataset[i][0])
for i, acao in enumerate(dataset.columns[1:]):
dataset[acao] = dataset[acao] * pesos[i] * dinheiro_total
dataset['soma valor'] = dataset.iloc[:,1:dataset.shape[1]].sum(axis = 1)
datas = dataset["Data de Negociação"]
dataset.drop(labels = ["Data de Negociação"], axis = 1, inplace = True)
dataset['taxa retorno'] = 0.0
for i in range(1, len(dataset)):
dataset['taxa retorno'][i] = np.log(dataset['soma valor'][i] / dataset['soma valor'][i - 1]) * 100
acoes_pesos = pd.DataFrame(data = {'Ações': colunas, 'Pesos': pesos})
return dataset, datas, acoes_pesos, dataset.loc[len(dataset) - 1]['soma valor']
Aqui é chamada a função de simulação, passando como parâmetros o dataset criado, valor de R$10.000,00 e o seed = 10 a ser utilizado na função de randomização.
dataset, datas, acoes_pesos, soma_valor = alocacao_ativos(acoes_port, 10000, 10)
dataset
| PRIO3 | JHSF3 | SANB1 | VIVT3 | ENEV3 | MULT3 | SIMH3 | UNIP6 | STBP3 | GOAU4 | soma valor | taxa retorno | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1874.925937 | 50.443831 | 1540.271898 | 1820.192204 | 1211.770663 | 546.435604 | NaN | 1848.697777 | 411.074560 | 214.736388 | 9518.548861 | 0.000000 |
| 1 | 1734.306524 | 48.634188 | 1529.373729 | 1784.096377 | 1125.215657 | 545.694824 | NaN | 1770.029803 | 411.074560 | 204.614816 | 9153.040479 | -3.915629 |
| 2 | 1563.858746 | 48.860394 | 1540.271898 | 1739.818918 | 1154.067294 | 572.856116 | NaN | 1809.363527 | 411.074560 | 224.659512 | 9064.830963 | -0.968392 |
| 3 | 1559.597479 | 48.860394 | 1604.449860 | 1799.497248 | 1182.919027 | 592.609792 | NaN | 1809.363527 | 411.074560 | 241.131895 | 9249.503781 | 2.016771 |
| 4 | 1610.731799 | 53.384500 | 1577.809905 | 1872.651479 | 1211.770663 | 586.313310 | NaN | 1809.363527 | 411.074560 | 232.994937 | 9366.094681 | 1.252631 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1875 | 94854.209140 | 131.425405 | 3419.597258 | 2256.711053 | 2984.371826 | 885.210863 | NaN | 48944.088372 | 2713.092119 | 197.470155 | 156386.176191 | -0.014504 |
| 1876 | 97325.702110 | 128.484730 | 3426.862627 | 2261.042559 | 2939.859107 | 879.284771 | NaN | 48718.313364 | 2691.456533 | 196.874773 | 158567.880574 | 1.385433 |
| 1877 | 96601.298578 | 126.675088 | 3333.623075 | 2253.823504 | 2903.439786 | 888.173909 | NaN | 48734.832974 | 2704.437926 | 197.271688 | 157743.576527 | -0.521199 |
| 1878 | 100606.824468 | 126.448888 | 3392.957419 | 2234.090942 | 2931.765903 | 881.507038 | NaN | 48894.529541 | 2695.783733 | 198.660938 | 161962.568872 | 2.639447 |
| 1879 | 101160.776286 | 121.246161 | 3386.902753 | 2232.647168 | 2915.579495 | 871.877191 | NaN | 47578.414049 | 2661.166961 | 198.660938 | 161127.271001 | -0.517070 |
1880 rows × 12 columns
acoes_pesos
| Ações | Pesos | |
|---|---|---|
| 0 | PRIO3 | 0.187493 |
| 1 | JHSF3 | 0.005044 |
| 2 | SANB1 | 0.154027 |
| 3 | VIVT3 | 0.182019 |
| 4 | ENEV3 | 0.121177 |
| 5 | MULT3 | 0.054644 |
| 6 | SIMH3 | 0.048145 |
| 7 | UNIP6 | 0.184870 |
| 8 | STBP3 | 0.041107 |
| 9 | GOAU4 | 0.021474 |
datas
0 2015-01-02
1 2015-01-05
2 2015-01-06
3 2015-01-07
4 2015-01-08
...
1875 2022-07-20
1876 2022-07-21
1877 2022-07-22
1878 2022-07-25
1879 2022-07-26
Name: Data de Negociação, Length: 1880, dtype: datetime64[ns]
soma_valor
161127.2710014405
Com base na ponderação aleatória e nos recursos investidos em cada ação.
figura = px.line(x = datas, y = dataset['taxa retorno'], title = 'Retorno diário do portfólio',
labels=dict(x="Data", y="Retorno %"))
figura.add_hline(y = dataset['taxa retorno'].mean(), line_color="red", line_dash="dot", )
figura.show()
Com base na ponderação aleatória e nos recursos investidos em cada ação.
figura = px.line(title = 'Evolução do patrimônio')
for i in dataset.drop(columns = ['soma valor', 'taxa retorno']).columns:
figura.add_scatter(x = datas, y = dataset[i], name = i)
figura.show()
Com base na ponderação aleatória e nos recursos investidos em cada ação. A linha verde representa a média da carteira.
figura = px.line(x = datas, y = dataset['soma valor'],
title = 'Evolução do patrimônio da Carteira',
labels=dict(x="Data", y="Valor R$"))
figura.add_hline(y = dataset['soma valor'].mean(),
line_color="green", line_dash="dot", )
figura.show()
Aqui, algumas estatísticas adicionais sobre o portfólio aleatório.
# Retorno
dataset.loc[len(dataset) - 1]['soma valor'] / dataset.loc[0]['soma valor'] - 1
15.927713809889948
# Desvio-Padrão
dataset['taxa retorno'].std()
2.4419700993463254
O índice de Sharpe dá uma ideia de retorno em relação ao risco. Quanto maior o índice, mais convervadora a carteira e menor o retorno.
# Sharpe Ratio
(dataset['taxa retorno'].mean() / dataset['taxa retorno'].std())
0.061620811077965895
Valor ganho, excluindo-se o valor investido na data inicial.
dinheiro_total = 10000
soma_valor - dinheiro_total
151127.2710014405
Por fim, utilizando-se dos conhecimentos e estruturas criadas nas seções anteriores, será feita uma simulação com 1000 carteiras aleatórias e obtido o portfólio que dá o melhor índice de Sharpe.
acoes_port
| Data de Negociação | PRIO3 | JHSF3 | SANB1 | VIVT3 | ENEV3 | MULT3 | SIMH3 | UNIP6 | STBP3 | GOAU4 | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2015-01-02 | 0.440000 | 2.23 | 12.720000 | 37.820000 | 5.989072 | 14.753333 | NaN | 3.357142 | 0.95 | 10.82 |
| 1 | 2015-01-05 | 0.407000 | 2.15 | 12.630000 | 37.070000 | 5.561281 | 14.733333 | NaN | 3.214285 | 0.95 | 10.31 |
| 2 | 2015-01-06 | 0.367000 | 2.16 | 12.720000 | 36.150002 | 5.703878 | 15.466666 | NaN | 3.285713 | 0.95 | 11.32 |
| 3 | 2015-01-07 | 0.366000 | 2.16 | 13.250000 | 37.389999 | 5.846475 | 16.000000 | NaN | 3.285713 | 0.95 | 12.15 |
| 4 | 2015-01-08 | 0.378000 | 2.36 | 13.030000 | 38.910000 | 5.989072 | 15.830000 | NaN | 3.285713 | 0.95 | 11.74 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1875 | 2022-07-20 | 22.260000 | 5.81 | 28.240000 | 46.889999 | 14.750000 | 23.900000 | 9.51 | 88.879997 | 6.27 | 9.95 |
| 1876 | 2022-07-21 | 22.840000 | 5.68 | 28.299999 | 46.980000 | 14.530000 | 23.740000 | 9.54 | 88.470001 | 6.22 | 9.92 |
| 1877 | 2022-07-22 | 22.670000 | 5.60 | 27.530001 | 46.830002 | 14.350000 | 23.980000 | 9.55 | 88.500000 | 6.25 | 9.94 |
| 1878 | 2022-07-25 | 23.610001 | 5.59 | 28.020000 | 46.419998 | 14.490000 | 23.799999 | 9.50 | 88.790001 | 6.23 | 10.01 |
| 1879 | 2022-07-26 | 23.740000 | 5.36 | 27.969999 | 46.389999 | 14.410000 | 23.540001 | 9.21 | 86.400002 | 6.15 | 10.01 |
1880 rows × 11 columns
log_ret = acoes_port.copy()
log_ret.drop(labels = ["Data de Negociação"], axis = 1, inplace = True)
log_ret = np.log(log_ret/log_ret.shift(1))
log_ret
| PRIO3 | JHSF3 | SANB1 | VIVT3 | ENEV3 | MULT3 | SIMH3 | UNIP6 | STBP3 | GOAU4 | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1 | -0.077962 | -0.036534 | -0.007101 | -0.020030 | -0.074108 | -0.001357 | NaN | -0.043485 | 0.000000 | -0.048282 |
| 2 | -0.103451 | 0.004640 | 0.007101 | -0.025131 | 0.025318 | 0.048575 | NaN | 0.021979 | 0.000000 | 0.093457 |
| 3 | -0.002729 | 0.000000 | 0.040822 | 0.033726 | 0.024693 | 0.033902 | NaN | 0.000000 | 0.000000 | 0.070758 |
| 4 | 0.032261 | 0.088553 | -0.016743 | 0.039848 | 0.024098 | -0.010682 | NaN | 0.000000 | 0.000000 | -0.034327 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1875 | -0.004482 | 0.012121 | -0.007057 | -0.013136 | 0.018475 | -0.001254 | 0.038590 | 0.007000 | 0.022582 | 0.002012 |
| 1876 | 0.025722 | -0.022629 | 0.002122 | 0.001918 | -0.015028 | -0.006717 | 0.003150 | -0.004624 | -0.008006 | -0.003020 |
| 1877 | -0.007471 | -0.014185 | -0.027585 | -0.003198 | -0.012465 | 0.010059 | 0.001048 | 0.000339 | 0.004812 | 0.002014 |
| 1878 | 0.040628 | -0.001787 | 0.017642 | -0.008794 | 0.009709 | -0.007535 | -0.005249 | 0.003271 | -0.003205 | 0.007018 |
| 1879 | 0.005491 | -0.042015 | -0.001786 | -0.000646 | -0.005536 | -0.010984 | -0.031002 | -0.027286 | -0.012924 | 0.000000 |
1880 rows × 10 columns
np.random.seed(42)
num_ports = 1000
all_weights = np.zeros((num_ports, len(acoes_port.columns[1:])))
ret_arr = np.zeros(num_ports)
vol_arr = np.zeros(num_ports)
sharpe_arr = np.zeros(num_ports)
for x in range(num_ports):
# Weights
weights = np.array(np.random.random(log_ret.shape[1]))
weights = weights/np.sum(weights)
# Save weights
all_weights[x,:] = weights
# Expected return
ret_arr[x] = np.sum((log_ret.mean() * weights))
# Expected volatility
vol_arr[x] = np.sqrt(np.dot(weights.T, np.dot(log_ret.cov(), weights)))
# Sharpe Ratio
sharpe_arr[x] = ret_arr[x]/vol_arr[x]
print("Max Sharpe Ratio: {}". format(sharpe_arr.max()))
print("Local do Max Sharpe Ratio: {}".format(sharpe_arr.argmax()))
Max Sharpe Ratio: 0.05765141684553508 Local do Max Sharpe Ratio: 842
# Pesos do Portfólio do Max Sharpe Ratio
print(all_weights[sharpe_arr.argmax(),:])
[0.18770876 0.08326079 0.07486367 0.00378353 0.0427857 0.10152048 0.03836667 0.32361831 0.10131189 0.04278019]
# salvando os dados do Max Sharpe Ratio
max_sr_ret = ret_arr[sharpe_arr.argmax()]
max_sr_vol = vol_arr[sharpe_arr.argmax()]
print(max_sr_ret)
print(max_sr_vol)
0.0011915637549334255 0.020668421005609828
plt.figure(figsize=(12,8))
plt.scatter(vol_arr, ret_arr, c=sharpe_arr, cmap='viridis')
plt.colorbar(label='Sharpe Ratio')
plt.xlabel('Volatilidade')
plt.ylabel('Retorno')
plt.scatter(max_sr_vol, max_sr_ret,c='black', s=200) # black dot
plt.show()
Nós podemos ver no gráfico acima o conjunto de portfólios simulados, pois o peso $w_i$ de cada ativo foi simulado e criamos um conjunto de $n = 1000$ carteiras e escolhemos no ponto vermelho a que tem maior Sharpe Ratio, que é a razão retorno sobre a volatilidade. Esse dado nos da uma noção do portfólio ponderado pelo risco.
def get_ret_vol_sr(weights):
weights = np.array(weights)
ret = np.sum(log_ret.mean() * weights)
vol = np.sqrt(np.dot(weights.T, np.dot(log_ret.cov(), weights)))
sr = ret/vol
return np.array([ret, vol, sr])
def neg_sharpe(weights):
# the number 2 is the sharpe ratio index from the get_ret_vol_sr
return get_ret_vol_sr(weights)[2] * -1
def check_sum(weights):
#return 0 if sum of the weights is 1
return np.sum(weights)-1
cons = ({'type': 'eq', 'fun': check_sum})
bounds = tuple((0, 1) for i in range(10))
init_guess = tuple(0.2 for i in range(10))
op_results = optimize.minimize(neg_sharpe, init_guess, method="SLSQP", bounds= bounds, constraints=cons)
print(op_results)
fun: -0.06883155328212162
jac: array([-2.32809223e-04, 1.17236711e-02, 6.38942234e-03, 7.94762000e-03,
3.25346459e-03, 1.51013732e-02, 6.32125419e-03, 6.62282109e-05,
1.28329732e-04, 3.72350272e-02])
message: 'Optimization terminated successfully'
nfev: 99
nit: 9
njev: 9
status: 0
success: True
x: array([2.42976077e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
0.00000000e+00, 1.30104261e-18, 0.00000000e+00, 6.53474890e-01,
1.03549033e-01, 0.00000000e+00])
# frontier_y = np.linspace(-0.0006, 0.0008, 200)
frontier_y = np.linspace(0.0002, 0.00135, 200)
def minimize_volatility(weights):
return get_ret_vol_sr(weights)[1]
frontier_x = []
for possible_return in frontier_y:
cons = ({'type':'eq', 'fun':check_sum},
{'type':'eq', 'fun': lambda w: get_ret_vol_sr(w)[0] - possible_return})
result = optimize.minimize(minimize_volatility,init_guess,method='SLSQP', bounds=bounds, constraints=cons)
frontier_x.append(result['fun'])
plt.figure(figsize=(12,8))
plt.scatter(vol_arr, ret_arr, c=sharpe_arr, cmap='viridis')
plt.colorbar(label='Sharpe Ratio')
plt.xlabel('Volatilidade')
plt.ylabel('Retorno')
plt.plot(frontier_x,frontier_y, 'r--', linewidth=3)
plt.scatter(max_sr_vol, max_sr_ret,c='black', s=200)
# plt.savefig('cover.png')
plt.show()